+++ /dev/null
-import Clutter from 'gi://Clutter';
-import GObject from 'gi://GObject';
-import Mtk from 'gi://Mtk';
-import St from 'gi://St';
-
-import * as Main from './main.js';
-
-const EDGE_THRESHOLD = 20;
-const DRAG_DISTANCE = 80;
-
-export const EdgeDragAction = GObject.registerClass({
- Signals: {
- 'activated': {},
- 'progress': {param_types: [GObject.TYPE_DOUBLE]},
- },
-}, class EdgeDragAction extends Clutter.GestureAction {
- _init(side, allowedModes) {
- super._init();
- this._side = side;
- this._allowedModes = allowedModes;
- this.set_n_touch_points(1);
- this.set_threshold_trigger_edge(Clutter.GestureTriggerEdge.AFTER);
- }
-
- _getMonitorRect(x, y) {
- const rect = new Mtk.Rectangle({x: x - 1, y: y - 1, width: 1, height: 1});
- let monitorIndex = global.display.get_monitor_index_for_rect(rect);
-
- return global.display.get_monitor_geometry(monitorIndex);
- }
-
- vfunc_gesture_prepare(_actor) {
- if (this.get_n_current_points() === 0)
- return false;
-
- if (!(this._allowedModes & Main.actionMode))
- return false;
-
- let [x, y] = this.get_press_coords(0);
- let monitorRect = this._getMonitorRect(x, y);
-
- return (this._side === St.Side.LEFT && x < monitorRect.x + EDGE_THRESHOLD) ||
- (this._side === St.Side.RIGHT && x > monitorRect.x + monitorRect.width - EDGE_THRESHOLD) ||
- (this._side === St.Side.TOP && y < monitorRect.y + EDGE_THRESHOLD) ||
- (this._side === St.Side.BOTTOM && y > monitorRect.y + monitorRect.height - EDGE_THRESHOLD);
- }
-
- vfunc_gesture_progress(_actor) {
- let [startX, startY] = this.get_press_coords(0);
- let [x, y] = this.get_motion_coords(0);
- let offsetX = Math.abs(x - startX);
- let offsetY = Math.abs(y - startY);
-
- if (offsetX < EDGE_THRESHOLD && offsetY < EDGE_THRESHOLD)
- return true;
-
- if ((offsetX > offsetY &&
- (this._side === St.Side.TOP || this._side === St.Side.BOTTOM)) ||
- (offsetY > offsetX &&
- (this._side === St.Side.LEFT || this._side === St.Side.RIGHT))) {
- this.cancel();
- return false;
- }
-
- if (this._side === St.Side.TOP ||
- this._side === St.Side.BOTTOM)
- this.emit('progress', offsetY);
- else
- this.emit('progress', offsetX);
-
- return true;
- }
-
- vfunc_gesture_end(_actor) {
- let [startX, startY] = this.get_press_coords(0);
- let [x, y] = this.get_motion_coords(0);
- let monitorRect = this._getMonitorRect(startX, startY);
-
- if ((this._side === St.Side.TOP && y > monitorRect.y + DRAG_DISTANCE) ||
- (this._side === St.Side.BOTTOM && y < monitorRect.y + monitorRect.height - DRAG_DISTANCE) ||
- (this._side === St.Side.LEFT && x > monitorRect.x + DRAG_DISTANCE) ||
- (this._side === St.Side.RIGHT && x < monitorRect.x + monitorRect.width - DRAG_DISTANCE))
- this.emit('activated');
- else
- this.cancel();
- }
-});
import St from 'gi://St';
import * as Signals from '../misc/signals.js';
-import * as EdgeDragAction from './edgeDragAction.js';
import * as InputSourceManager from './status/keyboard.js';
import * as IBusManager from '../misc/ibusManager.js';
import * as BoxPointer from './boxpointer.js';
this._syncEnabled();
});
- const mode = Shell.ActionMode.ALL & ~Shell.ActionMode.LOCK_SCREEN;
- const bottomDragAction = new EdgeDragAction.EdgeDragAction(St.Side.BOTTOM, mode);
- bottomDragAction.connect('activated', () => {
- if (this._keyboard)
- this._keyboard.gestureActivate(Main.layoutManager.bottomIndex);
+ const allowedModes = Shell.ActionMode.ALL & ~Shell.ActionMode.LOCK_SCREEN;
+ const bottomDragGesture = new Shell.EdgeDragGesture({
+ name: 'OSK show bottom drag',
+ side: St.Side.BOTTOM,
});
- bottomDragAction.connect('progress', (_action, progress) => {
- if (this._keyboard)
- this._keyboard.gestureProgress(progress);
+ bottomDragGesture.connect('may-recognize', () => {
+ return allowedModes & Main.actionMode;
});
- bottomDragAction.connect('gesture-cancel', () => {
- if (this._keyboard)
- this._keyboard.gestureCancel();
+ bottomDragGesture.connect('progress', (_action, progress) => {
+ this._keyboard?.gestureProgress(progress);
});
- global.stage.add_action_full('osk', Clutter.EventPhase.CAPTURE, bottomDragAction);
- this._bottomDragAction = bottomDragAction;
+ bottomDragGesture.connect('end', () => {
+ this._keyboard?.gestureActivate(Main.layoutManager.bottomIndex);
+ });
+ bottomDragGesture.connect('cancel', () => {
+ this._keyboard?.gestureCancel();
+ });
+ global.stage.add_action(bottomDragGesture);
+ this._bottomDragGesture = bottomDragGesture;
this._syncEnabled();
}
this._keyboard = new Keyboard();
this._keyboard.connect('visibility-changed', () => {
this.emit('visibility-changed');
- this._bottomDragAction.enabled = !this._keyboard.visible;
+ this._bottomDragGesture.enabled = !this._keyboard.visible;
});
} else if (!enabled && this._keyboard) {
this._keyboard.setCursorLocation(null);
this._keyboard.destroy();
this._keyboard = null;
- this._bottomDragAction.enabled = true;
+ this._bottomDragGesture.enabled = true;
}
}
import * as ModalDialog from './modalDialog.js';
import * as WindowMenu from './windowMenu.js';
import * as PadOsd from './padOsd.js';
-import * as EdgeDragAction from './edgeDragAction.js';
import * as CloseDialog from './closeDialog.js';
import * as SwitchMonitor from './switchMonitor.js';
import * as IBusManager from '../misc/ibusManager.js';
if (Main.sessionMode.hasWorkspaces)
this._workspaceTracker = new WorkspaceTracker(this);
- let mode = Shell.ActionMode.NORMAL;
- let topDragAction = new EdgeDragAction.EdgeDragAction(St.Side.TOP, mode);
- topDragAction.connect('activated', () => {
+ const allowedModes = Shell.ActionMode.NORMAL;
+ const topDragGesture = new Shell.EdgeDragGesture({
+ name: 'Window unfullscreen top drag',
+ side: St.Side.TOP,
+ });
+ topDragGesture.connect('may-recognize', () => {
+ return allowedModes & Main.actionMode;
+ });
+ topDragGesture.connect('end', () => {
let currentWindow = global.display.focus_window;
if (currentWindow)
currentWindow.unmake_fullscreen();
let updateUnfullscreenGesture = () => {
let currentWindow = global.display.focus_window;
- topDragAction.enabled = currentWindow && currentWindow.is_fullscreen();
+ topDragGesture.enabled = currentWindow && currentWindow.is_fullscreen();
};
global.display.connect('notify::focus-window', updateUnfullscreenGesture);
global.display.connect('in-fullscreen-changed', updateUnfullscreenGesture);
updateUnfullscreenGesture();
- global.stage.add_action_full('unfullscreen', Clutter.EventPhase.CAPTURE, topDragAction);
+ global.stage.add_action(topDragGesture);
this._workspaceAnimation =
new WorkspaceAnimation.WorkspaceAnimationController();
--- /dev/null
+/*
+ * Copyright (C) 2025 Jonas Dreßler <verdre@v0yd.nl>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <clutter/clutter.h>
+
+#include "shell-edge-drag-gesture.h"
+#include "shell-global.h"
+
+#define EDGE_THRESHOLD 20
+#define DRAG_DISTANCE 80
+
+struct _ShellEdgeDragGesture
+{
+ ClutterGesture parent;
+
+ StSide side;
+};
+
+enum
+{
+ PROP_0,
+
+ PROP_SIDE,
+
+ PROP_LAST
+};
+
+enum
+{
+ PROGRESS,
+
+ LAST_SIGNAL
+};
+
+static GParamSpec *obj_props[PROP_LAST] = { NULL, };
+static unsigned int obj_signals[LAST_SIGNAL] = { 0, };
+
+G_DEFINE_FINAL_TYPE (ShellEdgeDragGesture, shell_edge_drag_gesture, CLUTTER_TYPE_GESTURE);
+
+static gboolean
+get_monitor_for_coords (ShellEdgeDragGesture *self,
+ graphene_point_t *coords,
+ MtkRectangle *monitor_geometry_out)
+{
+ MetaDisplay *display = shell_global_get_display (shell_global_get ());
+ unsigned int monitor_index;
+ MtkRectangle rect;
+
+ rect.x = coords->x - 1;
+ rect.y = coords->y - 1;
+ rect.width = 1;
+ rect.height = 1;
+
+ monitor_index = meta_display_get_monitor_index_for_rect (display, &rect);
+ if (monitor_index == -1)
+ return FALSE;
+
+ meta_display_get_monitor_geometry (display, monitor_index, monitor_geometry_out);
+ return TRUE;
+}
+
+static gboolean
+is_near_monitor_edge (ShellEdgeDragGesture *self,
+ unsigned int point)
+{
+ graphene_point_t coords;
+ MtkRectangle monitor_geometry;
+
+ clutter_gesture_get_point_coords_abs (CLUTTER_GESTURE (self),
+ point,
+ &coords);
+
+ if (!get_monitor_for_coords (self, &coords, &monitor_geometry))
+ {
+ g_assert_not_reached ();
+ return FALSE;
+ }
+
+ switch (self->side)
+ {
+ case ST_SIDE_LEFT:
+ return coords.x < (float) (monitor_geometry.x + EDGE_THRESHOLD);
+ case ST_SIDE_RIGHT:
+ return coords.x > (float) (monitor_geometry.x + monitor_geometry.width - EDGE_THRESHOLD);
+ case ST_SIDE_TOP:
+ return coords.y < (float) (monitor_geometry.y + EDGE_THRESHOLD);
+ case ST_SIDE_BOTTOM:
+ return coords.y > (float) (monitor_geometry.y + monitor_geometry.height - EDGE_THRESHOLD);
+ }
+
+ g_assert_not_reached ();
+ return FALSE;
+}
+
+static gboolean
+exceeds_cancel_threshold (ShellEdgeDragGesture *self,
+ unsigned int point)
+{
+ graphene_point_t begin_coords, latest_coords;
+ float distance_x, distance_y;
+
+ clutter_gesture_get_point_begin_coords_abs (CLUTTER_GESTURE (self),
+ point,
+ &begin_coords);
+
+ clutter_gesture_get_point_coords_abs (CLUTTER_GESTURE (self),
+ point,
+ &latest_coords);
+
+ graphene_point_distance (&latest_coords, &begin_coords, &distance_x, &distance_y);
+
+ switch (self->side)
+ {
+ case ST_SIDE_LEFT:
+ case ST_SIDE_RIGHT:
+ return distance_y > distance_x;
+
+ case ST_SIDE_TOP:
+ case ST_SIDE_BOTTOM:
+ return distance_x > distance_y;
+ }
+
+ g_assert_not_reached ();
+ return FALSE;
+}
+
+static gboolean
+passes_distance_needed (ShellEdgeDragGesture *self,
+ unsigned int sequence)
+{
+ graphene_point_t begin_coords, latest_coords;
+ MtkRectangle monitor_geometry;
+
+ clutter_gesture_get_point_begin_coords_abs (CLUTTER_GESTURE (self),
+ sequence,
+ &begin_coords);
+
+ if (!get_monitor_for_coords (self, &begin_coords, &monitor_geometry))
+ {
+ g_assert_not_reached ();
+ return FALSE;
+ }
+
+ clutter_gesture_get_point_coords_abs (CLUTTER_GESTURE (self),
+ sequence,
+ &latest_coords);
+
+ switch (self->side)
+ {
+ case ST_SIDE_LEFT:
+ return latest_coords.x > (float) (monitor_geometry.x + DRAG_DISTANCE);
+ case ST_SIDE_RIGHT:
+ return latest_coords.x < (float) (monitor_geometry.x + monitor_geometry.width - DRAG_DISTANCE);
+ case ST_SIDE_TOP:
+ return latest_coords.y > (float) (monitor_geometry.y + DRAG_DISTANCE);
+ case ST_SIDE_BOTTOM:
+ return latest_coords.y < (float) (monitor_geometry.y + monitor_geometry.height - DRAG_DISTANCE);
+ }
+
+ g_assert_not_reached ();
+ return FALSE;
+}
+
+static gboolean
+shell_edge_drag_gesture_should_handle_sequence (ClutterGesture *gesture,
+ const ClutterEvent *sequence_begin_event)
+{
+ ClutterEventType event_type = clutter_event_type (sequence_begin_event);
+
+ if (event_type == CLUTTER_TOUCH_BEGIN)
+ return TRUE;
+
+ return FALSE;
+}
+
+static void
+shell_edge_drag_gesture_point_began (ClutterGesture *gesture,
+ unsigned int point)
+{
+ ShellEdgeDragGesture *self = SHELL_EDGE_DRAG_GESTURE (gesture);
+ unsigned int n_points = clutter_gesture_get_n_points (gesture);
+
+ if (n_points > 1 ||
+ !is_near_monitor_edge (self, point))
+ {
+ clutter_gesture_set_state (gesture, CLUTTER_GESTURE_STATE_CANCELLED);
+ return;
+ }
+}
+
+static void
+shell_edge_drag_gesture_point_moved (ClutterGesture *gesture,
+ unsigned int point)
+{
+ ShellEdgeDragGesture *self = SHELL_EDGE_DRAG_GESTURE (gesture);
+
+ if (clutter_gesture_get_state (gesture) == CLUTTER_GESTURE_STATE_POSSIBLE &&
+ is_near_monitor_edge (self, point))
+ return;
+
+ if (exceeds_cancel_threshold (self, point))
+ {
+ clutter_gesture_set_state (gesture, CLUTTER_GESTURE_STATE_CANCELLED);
+ return;
+ }
+
+ if (clutter_gesture_get_state (gesture) == CLUTTER_GESTURE_STATE_POSSIBLE)
+ clutter_gesture_set_state (gesture, CLUTTER_GESTURE_STATE_RECOGNIZING);
+
+ if (clutter_gesture_get_state (gesture) == CLUTTER_GESTURE_STATE_RECOGNIZING)
+ {
+ graphene_point_t begin_coords, latest_coords;
+ float distance_x, distance_y;
+
+ clutter_gesture_get_point_begin_coords_abs (gesture,
+ point,
+ &begin_coords);
+
+ clutter_gesture_get_point_coords_abs (gesture,
+ point,
+ &latest_coords);
+
+ graphene_point_distance (&latest_coords, &begin_coords, &distance_x, &distance_y);
+
+ switch (self->side)
+ {
+ case ST_SIDE_LEFT:
+ case ST_SIDE_RIGHT:
+ g_signal_emit (self, obj_signals[PROGRESS], 0, distance_x);
+ break;
+
+ case ST_SIDE_TOP:
+ case ST_SIDE_BOTTOM:
+ g_signal_emit (self, obj_signals[PROGRESS], 0, distance_y);
+ break;
+ }
+
+ if (passes_distance_needed (self, point))
+ clutter_gesture_set_state (gesture, CLUTTER_GESTURE_STATE_COMPLETED);
+ }
+}
+
+static void
+shell_edge_drag_gesture_point_ended (ClutterGesture *gesture,
+ unsigned int point)
+{
+ clutter_gesture_set_state (gesture, CLUTTER_GESTURE_STATE_CANCELLED);
+}
+
+static void
+shell_edge_drag_gesture_set_property (GObject *gobject,
+ unsigned int prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ ShellEdgeDragGesture *self = SHELL_EDGE_DRAG_GESTURE (gobject);
+
+ switch (prop_id)
+ {
+ case PROP_SIDE:
+ shell_edge_drag_gesture_set_side (self, g_value_get_enum (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+shell_edge_drag_gesture_get_property (GObject *gobject,
+ unsigned int prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ ShellEdgeDragGesture *self = SHELL_EDGE_DRAG_GESTURE (gobject);
+
+ switch (prop_id)
+ {
+ case PROP_SIDE:
+ g_value_set_enum (value, shell_edge_drag_gesture_get_side (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+shell_edge_drag_gesture_init (ShellEdgeDragGesture *self)
+{
+ self->side = ST_SIDE_TOP;
+}
+
+static void
+shell_edge_drag_gesture_class_init (ShellEdgeDragGestureClass *klass)
+{
+ ClutterGestureClass *gesture_class = CLUTTER_GESTURE_CLASS (klass);
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gesture_class->should_handle_sequence = shell_edge_drag_gesture_should_handle_sequence;
+ gesture_class->point_began = shell_edge_drag_gesture_point_began;
+ gesture_class->point_moved = shell_edge_drag_gesture_point_moved;
+ gesture_class->point_ended = shell_edge_drag_gesture_point_ended;
+
+ gobject_class->set_property = shell_edge_drag_gesture_set_property;
+ gobject_class->get_property = shell_edge_drag_gesture_get_property;
+
+ /**
+ * ShellEdgeDragGesture:side:
+ *
+ * Edge that the gesture may start at. Defaults to the top edge.
+ */
+ obj_props[PROP_SIDE] =
+ g_param_spec_enum ("side", NULL, NULL,
+ CLUTTER_TYPE_GESTURE_STATE,
+ ST_SIDE_TOP,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS |
+ G_PARAM_EXPLICIT_NOTIFY);
+
+ g_object_class_install_properties (gobject_class, PROP_LAST, obj_props);
+
+ /**
+ * ShellEdgeDragGesture::progress:
+ * @gesture: the #ShellEdgeDragGesture that emitted the signal
+ * @progress_px: the progress of the gesture in pixels in the selected direction
+ *
+ * The ::progress signal is emitted when the edge drag has moved
+ */
+ obj_signals[PROGRESS] =
+ g_signal_new ("progress",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1,
+ G_TYPE_FLOAT);
+}
+
+/**
+ * shell_edge_drag_gesture_set_side:
+ * @self: a #ShellEdgeDragGesture
+ * @side: the side
+ *
+ * Sets the edge of the monitor that the edge drag may start at.
+ */
+void
+shell_edge_drag_gesture_set_side (ShellEdgeDragGesture *self,
+ StSide side)
+{
+ g_return_if_fail (SHELL_IS_EDGE_DRAG_GESTURE (self));
+ g_return_if_fail (side >= ST_SIDE_TOP && side <= ST_SIDE_LEFT);
+
+ if (self->side == side)
+ return;
+
+ self->side = side;
+
+ g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_SIDE]);
+}
+
+/**
+ * shell_edge_drag_gesture_get_side:
+ * @self: a #ShellEdgeDragGesture
+ *
+ * Gets the edge of the monitor that the edge drag may start at.
+ *
+ * Returns: the side that the edge drag may start at
+ */
+StSide
+shell_edge_drag_gesture_get_side (ShellEdgeDragGesture *self)
+{
+ g_return_val_if_fail (SHELL_IS_EDGE_DRAG_GESTURE (self), ST_SIDE_TOP);
+
+ return self->side;
+}